using System;
using System.Collections.Generic;

public static class GenericBinaryHelper
{
    internal delegate void From<T>(out T entity, NetBuffer buffer);
    internal delegate void To<T>(T entity, NetBuffer buffer);
    internal static class Specializer<T>
    {
        internal static From<T> From;
        internal static To<T> To;
        internal static void CallFrom(out T entity, NetBuffer buffer)
        {
            if (From != null)
            {
                From(out entity, buffer);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        internal static void CallTo(T entity, NetBuffer buffer)
        {
            if (To != null)
            {
                To(entity, buffer);
            }
            else
            {
                throw new NotImplementedException();
            }
        }
    }
    public static void DoInit()
    {
        Specializer<char>.From = BinaryHelper.ReadFromBuffer;
        Specializer<bool>.From = BinaryHelper.ReadFromBuffer;
        Specializer<float>.From = BinaryHelper.ReadFromBuffer;
        Specializer<double>.From = BinaryHelper.ReadFromBuffer;
        Specializer<sbyte>.From = BinaryHelper.ReadFromBuffer;
        Specializer<byte>.From = BinaryHelper.ReadFromBuffer;
        Specializer<short>.From = BinaryHelper.ReadFromBuffer;
        Specializer<ushort>.From = BinaryHelper.ReadFromBuffer;
        Specializer<int>.From = BinaryHelper.ReadFromBuffer;
        Specializer<uint>.From = BinaryHelper.ReadFromBuffer;
        Specializer<long>.From = BinaryHelper.ReadFromBuffer;
        Specializer<ulong>.From = BinaryHelper.ReadFromBuffer;
        Specializer<string>.From = BinaryHelper.ReadFromBuffer;
        Specializer<byte[]>.From = BinaryHelper.ReadFromBuffer;
        Specializer<char>.To = BinaryHelper.WriteToBuffer;
        Specializer<bool>.To = BinaryHelper.WriteToBuffer;
        Specializer<float>.To = BinaryHelper.WriteToBuffer;
        Specializer<double>.To = BinaryHelper.WriteToBuffer;
        Specializer<sbyte>.To = BinaryHelper.WriteToBuffer;
        Specializer<byte>.To = BinaryHelper.WriteToBuffer;
        Specializer<short>.To = BinaryHelper.WriteToBuffer;
        Specializer<ushort>.To = BinaryHelper.WriteToBuffer;
        Specializer<int>.To = BinaryHelper.WriteToBuffer;
        Specializer<uint>.To = BinaryHelper.WriteToBuffer;
        Specializer<long>.To = BinaryHelper.WriteToBuffer;
        Specializer<ulong>.To = BinaryHelper.WriteToBuffer;
        Specializer<string>.To = BinaryHelper.WriteToBuffer;
        Specializer<byte[]>.To = BinaryHelper.WriteToBuffer;
    }
}

public static class BinaryHelper
{
    public interface ISerializable
    {
        void LoadFromBuffer(NetBuffer buffer);
        void SaveToBuffer(NetBuffer buffer);
    };

    private static void ReadFromBuffer<T>(out T entity, NetBuffer buffer)
    {
        GenericBinaryHelper.Specializer<T>.CallFrom(out entity, buffer);
    }
    private static void WriteToBuffer<T>(T entity, NetBuffer buffer)
    {
        GenericBinaryHelper.Specializer<T>.CallTo(entity, buffer);
    }

    public static void ReadFromBuffer(out char entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out bool entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out float entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out double entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out sbyte entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out byte entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out short entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out ushort entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out int entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out uint entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out long entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out ulong entity, NetBuffer buffer)
    {
        buffer.Read(out entity);
    }
    public static void ReadFromBuffer(out string entity, NetBuffer buffer)
    {
        uint length = Utils.ReadFromBuffer7BitEncodedInt(buffer);
        if (length != 0)
        {
            entity = System.Text.Encoding.UTF8.GetString(buffer.GetBuffer(), buffer.Skip((int)length), (int)length);
        }
        else
        {
            entity = null;
        }
    }
    public static void ReadFromBuffer(out byte[] entity, NetBuffer buffer)
    {
        uint length = Utils.ReadFromBuffer7BitEncodedInt(buffer);
        if (length != 0)
        {
            entity = new byte[length];
            buffer.Take(entity, 0, (int)length);
        }
        else
        {
            entity = null;
        }
    }

    public static void WriteToBuffer(char entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(bool entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(float entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(double entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(sbyte entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(byte entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(short entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(ushort entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(int entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(uint entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(long entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(ulong entity, NetBuffer buffer)
    {
        buffer.Write(entity);
    }
    public static void WriteToBuffer(string entity, NetBuffer buffer)
    {
        WriteToBuffer(entity != null && entity.Length != 0 ? System.Text.Encoding.UTF8.GetBytes(entity) : null, buffer);
    }
    public static void WriteToBuffer(byte[] entity, NetBuffer buffer)
    {
        int length = entity != null ? entity.Length : 0;
        Utils.WriteToBuffer7BitEncodedInt((uint)length, buffer);
        if (length != 0)
        {
            buffer.Append(entity, 0, length);
        }
    }

    public static void SequenceReadFromBuffer<T>(out List<T> entity, NetBuffer buffer)
    {
        uint count = Utils.ReadFromBuffer7BitEncodedInt(buffer);
        if (count != 0)
        {
            entity = new List<T>((int)count);
            for (uint index = 0; index < count; ++index)
            {
                ReadFromBuffer(out T value, buffer);
                entity.Add(value);
            }
        }
        else
        {
            entity = null;
        }
    }
    public static void SequenceWriteToBuffer<T>(List<T> entity, NetBuffer buffer)
    {
        int count = entity != null ? entity.Count : 0;
        Utils.WriteToBuffer7BitEncodedInt((uint)count, buffer);
        if (count != 0)
        {
            foreach (var value in entity)
            {
                WriteToBuffer(value, buffer);
            }
        }
    }

    public static void BlockSequenceReadFromBuffer<T>(out List<T> entity, NetBuffer buffer) where T : ISerializable, new()
    {
        uint count = Utils.ReadFromBuffer7BitEncodedInt(buffer);
        if (count != 0)
        {
            entity = new List<T>((int)count);
            for (uint index = 0; index < count; ++index)
            {
                LoadFromBuffer(out T value, buffer);
                entity.Add(value);
            }
        }
        else
        {
            entity = null;
        }
    }
    public static void BlockSequenceWriteToBuffer<T>(List<T> entity, NetBuffer buffer) where T : ISerializable, new()
    {
        int count = entity != null ? entity.Count : 0;
        Utils.WriteToBuffer7BitEncodedInt((uint)count, buffer);
        if (count != 0)
        {
            foreach (var value in entity)
            {
                SaveToBuffer(value, buffer);
            }
        }
    }

    public static void AssociativeReadFromBuffer<K, V>(out Dictionary<K, V> entity, NetBuffer buffer)
    {
        uint count = Utils.ReadFromBuffer7BitEncodedInt(buffer);
        if (count != 0)
        {
            entity = new Dictionary<K, V>((int)count);
            for (uint index = 0; index < count; ++index)
            {
                ReadFromBuffer(out K key, buffer);
                ReadFromBuffer(out V value, buffer);
                entity[key] = value;
            }
        }
        else
        {
            entity = null;
        }
    }
    public static void AssociativeWriteToBuffer<K, V>(Dictionary<K, V> entity, NetBuffer buffer)
    {
        int count = entity != null ? entity.Count : 0;
        Utils.WriteToBuffer7BitEncodedInt((uint)count, buffer);
        if (count != 0)
        {
            foreach (var pair in entity)
            {
                WriteToBuffer(pair.Key, buffer);
                WriteToBuffer(pair.Value, buffer);
            }
        }
    }

    public static void BlockAssociativeReadFromBuffer<K, V>(out Dictionary<K, V> entity, NetBuffer buffer) where V : ISerializable, new()
    {
        uint count = Utils.ReadFromBuffer7BitEncodedInt(buffer);
        if (count != 0)
        {
            entity = new Dictionary<K, V>((int)count);
            for (uint index = 0; index < count; ++index)
            {
                ReadFromBuffer(out K key, buffer);
                LoadFromBuffer(out V value, buffer);
                entity[key] = value;
            }
        }
        else
        {
            entity = null;
        }
    }
    public static void BlockAssociativeWriteToBuffer<K, V>(Dictionary<K, V> entity, NetBuffer buffer) where V : ISerializable, new()
    {
        int count = entity != null ? entity.Count : 0;
        Utils.WriteToBuffer7BitEncodedInt((uint)count, buffer);
        if (count != 0)
        {
            foreach (var pair in entity)
            {
                WriteToBuffer(pair.Key, buffer);
                SaveToBuffer(pair.Value, buffer);
            }
        }
    }

    public static void UniqueReadFromBuffer<T>(out HashSet<T> entity, NetBuffer buffer)
    {
        uint count = Utils.ReadFromBuffer7BitEncodedInt(buffer);
        if (count != 0)
        {
            entity = new HashSet<T>(/*(int)size*/);
            for (uint index = 0; index < count; ++index)
            {
                ReadFromBuffer(out T value, buffer);
                entity.Add(value);
            }
        }
        else
        {
            entity = null;
        }
    }
    public static void UniqueWriteToBuffer<T>(HashSet<T> entity, NetBuffer buffer)
    {
        int count = entity != null ? entity.Count : 0;
        Utils.WriteToBuffer7BitEncodedInt((uint)count, buffer);
        if (count != 0)
        {
            foreach (var value in entity)
            {
                WriteToBuffer(value, buffer);
            }
        }
    }

    public static void LoadFromBuffer<T>(out T entity, NetBuffer buffer) where T : ISerializable, new()
    {
        entity = new T();
        entity.LoadFromBuffer(buffer);
    }
    public static void SaveToBuffer<T>(T entity, NetBuffer buffer) where T : ISerializable, new()
    {
        entity = entity != null ? entity : new T();
        entity.SaveToBuffer(buffer);
    }
};
